%token TOKEN_STRING_LITERAL
%token TOKEN_CHARACTER_LITERAL
%token TOKEN_INTEGER_LITERAL
%token TOKEN_GE
%token TOKEN_LE
%token TOKEN_GT
%token TOKEN_LT
%token TOKEN_EQ
%token TOKEN_NE
%token TOKEN_ASSIGN
%token TOKEN_IDENTIFIER
%token TOKEN_COMMA
%token TOKEN_SEMICOLON
%token TOKEN_TRUE 
%token TOKEN_FALSE
%token TOKEN_IF    
%token TOKEN_WHILE 
%token TOKEN_RETURN
%token TOKEN_ELSE  
%token TOKEN_VOID  
%token TOKEN_PRINT 
%token TOKEN_INTEGER
%token TOKEN_CHAR   
%token TOKEN_STRING 
%token TOKEN_BOOLEAN
%token TOKEN_LOGICALOR 
%token TOKEN_LOGICALAND
%token TOKEN_OPENPAREN 
%token TOKEN_CLOSEPAREN
%token TOKEN_OPENCURLY 
%token TOKEN_CLOSECURLY
%token TOKEN_STAR
%token TOKEN_SLASH
%token TOKEN_PERCENT
%token TOKEN_PLUS
%token TOKEN_MINUS
%token TOKEN_CARET
%token TOKEN_ERROR
%token TOKEN_DECREMENT
%token TOKEN_INCREMENT

%{

#define YYERROR_VERBOSE 1

extern char * yytext;
extern int yylex();
extern int yyerror( char * str );
extern int yylineno;

#include "ast.h"
#include "main.h"

extern struct decl * parser_root;
%}

%%
program
  : decl_list
  { parser_root = $1; return 0;}
  ;

decl_list
  : decl decl_list
  { struct decl * decl1 = $1;
    decl1->next = $2; 
    $$ = decl1;
  }
  |
  {$$ = 0;} 
  ;

decl
  : type param TOKEN_SEMICOLON
  { $$ = decl_create($2, 0, $1, 0, 0, 0, 0);}  
  | type param TOKEN_OPENPAREN param_list TOKEN_CLOSEPAREN TOKEN_SEMICOLON
  { $$ = decl_create($2, 0, type_create(TYPE_KIND_FUNCTION,$4, $1), 0, 0, 0, 0);} 
  | type param TOKEN_OPENPAREN param_list TOKEN_CLOSEPAREN block
  { $$ = decl_create($2, 0, type_create(TYPE_KIND_FUNCTION,$4, $1), 0, $6, 0, 0);} 
  | type param TOKEN_ASSIGN expr TOKEN_SEMICOLON
  { $$ = decl_create($2, 0, $1, $4, 0, 0, 0);} 
  ;
  
block
  : TOKEN_OPENCURLY stmt_list TOKEN_CLOSECURLY
  { $$ = stmt_create(STMT_KIND_BLOCK, 0, 0, $2, 0, 0);}
  ;

stmt_list
  : stmt stmt_list         
  {struct stmt * stmt1 = $1;
   stmt1->next = $2; 
   $$ = stmt1;}
  |
  {$$ = 0;} 
  ;

stmt
  :  matched
  { $$ = $1;}
  | unmatched
  { $$ = $1;}
  ;

matched
  : expr TOKEN_SEMICOLON
  {$$ = stmt_create(STMT_KIND_EXPR, 0, $1, 0, 0, 0);}
  | decl
  {$$ = stmt_create(STMT_KIND_DECL, $1, 0, 0, 0, 0);}
  | TOKEN_SEMICOLON
  { $$ = 0; }
  | return_stmt
  { $$ = $1;}
  | block
  { $$ = $1;}
  | print
  { $$ = $1;}
  | TOKEN_IF TOKEN_OPENPAREN expr TOKEN_CLOSEPAREN matched TOKEN_ELSE matched
  { $$ = stmt_create(STMT_KIND_IF_ELSE, 0, $3, $5, $7, 0);} 
  | while_matched
  { $$ = $1;}
  ;

return_stmt
  :  TOKEN_RETURN expr TOKEN_SEMICOLON
  {$$ = stmt_create(STMT_KIND_RETURN, 0, $2, 0, 0, 0);}
  | TOKEN_RETURN TOKEN_SEMICOLON
  {$$ = stmt_create(STMT_KIND_RETURN, 0, 0, 0, 0, 0);}

while_unmatched
  : TOKEN_WHILE TOKEN_OPENPAREN expr TOKEN_CLOSEPAREN unmatched
  {$$ = stmt_create(STMT_KIND_WHILE, 0, $3, $5, 0, 0);}
  ;

while_matched
  : TOKEN_WHILE TOKEN_OPENPAREN expr TOKEN_CLOSEPAREN matched
  {$$ = stmt_create(STMT_KIND_WHILE, 0, $3, $5, 0, 0);}
  ;

unmatched
  : TOKEN_IF TOKEN_OPENPAREN expr TOKEN_CLOSEPAREN stmt
  { $$ = stmt_create(STMT_KIND_IF_ELSE, 0, $3, $5, 0, 0);}
  | TOKEN_IF TOKEN_OPENPAREN expr TOKEN_CLOSEPAREN matched TOKEN_ELSE unmatched
  { $$ = stmt_create(STMT_KIND_IF_ELSE, 0, $3, $5, $7, 0);}
  | while_unmatched
  { $$ = $1;}
  ;

print
  : TOKEN_PRINT arg_list TOKEN_SEMICOLON
  { $$ = stmt_create(STMT_KIND_PRINT, 0, $2, 0, 0, 0);}
  ;

arg_list
  : arg_list_not_empty
  { $$ = $1;}
  |  
  {$$ = 0;} 
  ;
  
arg_list_not_empty
  : expr TOKEN_COMMA arg_list_not_empty
  { $$ = expr_create(EXPR_KIND_ARG, $1, $3, 0, 0, 0, 0);}
  | expr
  { $$ = expr_create(EXPR_KIND_ARG, $1, 0, 0, 0, 0, 0);}
  ;

type
  : TOKEN_INTEGER
  { $$ = type_create(TYPE_KIND_INTEGER, 0, 0);}
  | TOKEN_CHAR   
  {$$ = type_create(TYPE_KIND_CHARACTER, 0, 0);}
  | TOKEN_STRING 
  {$$ = type_create(TYPE_KIND_STRING, 0, 0);}
  | TOKEN_BOOLEAN
  {$$ = type_create(TYPE_KIND_BOOLEAN, 0, 0);}
  | TOKEN_VOID
  {$$ = type_create(TYPE_KIND_VOID, 0, 0);}
  ;

expr
  : expr TOKEN_ASSIGN logor
  { $$ = expr_create(EXPR_KIND_ASSIGN, $1, $3, 0, 0, 0, 0);}
  | logor 
  { $$ = $1;}
  ;

logor
  : logor TOKEN_LOGICALOR logand
  { $$ = expr_create(EXPR_KIND_LOGICALOR, $1, $3, 0,0,0,0); }
  | logand
  { $$ = $1; }
  ;
 
logand: logand TOKEN_LOGICALAND compare
{$$ = expr_create(EXPR_KIND_LOGICALAND, $1, $3, 0,0,0,0); }
  | compare
  {$$=$1;}
  ;
  
compare
  : compare TOKEN_LT term
  {$$ = expr_create(EXPR_KIND_LT, $1, $3, 0,0,0,0); }
  | compare TOKEN_LE term
  {$$ = expr_create(EXPR_KIND_LE, $1, $3, 0,0,0,0); }
  | compare TOKEN_GE term
  {$$ = expr_create(EXPR_KIND_GE, $1, $3, 0,0,0,0); }
  | compare TOKEN_GT term
  {$$ = expr_create(EXPR_KIND_GT, $1, $3, 0,0,0,0); }
  | compare TOKEN_NE term
  {$$ = expr_create(EXPR_KIND_NE, $1, $3, 0,0,0,0); }
  | compare TOKEN_EQ term
  {$$ = expr_create(EXPR_KIND_EQ, $1, $3, 0,0,0,0); }       
  | term
  {$$=$1;}
  ;
  
term
  : term TOKEN_PLUS factor
  {$$ = expr_create(EXPR_KIND_ADD, $1, $3, 0,0,0,0); }   
  | term TOKEN_MINUS factor
  {$$ = expr_create(EXPR_KIND_SUB, $1, $3, 0,0,0,0); }
  | factor
  {$$=$1;}

factor
  : factor TOKEN_STAR      expon 
  {$$ = expr_create(EXPR_KIND_MUL, $1, $3, 0,0,0,0); }
  | factor TOKEN_SLASH     expon
  {$$ = expr_create(EXPR_KIND_DIV, $1, $3, 0,0,0,0); }
  | factor TOKEN_PERCENT   expon
  { $$ = expr_create(EXPR_KIND_MOD, $1, $3, 0,0,0,0); }
  | expon
  { $$ = $1; }
  ;
  
expon
  : unary_minus TOKEN_CARET expon
  { $$ = expr_create(EXPR_KIND_POW, $1, $3, 0,0,0,0); }
  | unary_minus
  { $$ = $1;}
  ;
  
unary_minus
  : TOKEN_MINUS postfix
  { $$ = expr_create(EXPR_KIND_NEG, 0, $2, 0,0,0,0); }
  | postfix
  { $$ = $1;}
  ;
  
postfix
  : exprbase TOKEN_DECREMENT
  { $$ = expr_create(EXPR_KIND_DECR, $1, 0, 0,0,0,0); }
  | exprbase TOKEN_INCREMENT
  { $$ = expr_create(EXPR_KIND_INCR, $1, 0, 0,0,0,0); }
  | exprbase
  { $$ = $1;}
  ;
  
exprbase
  : TOKEN_OPENPAREN expr TOKEN_CLOSEPAREN
  { $$ = $2;}
  | name
  { $$ = $1;}
  | constant
  { $$ = $1;}
  | functioncall
  { $$ = $1;}
  ;
  
constant
  : TOKEN_STRING_LITERAL
  { $$ = expr_create(EXPR_KIND_STRING, 0, 0, strdup(yytext), 1, 0, 0);}
  | TOKEN_CHARACTER_LITERAL
  { char toStore;
    if (yytext[1] == '\\')
    {
      if (yytext[2] == 'n')
        toStore = '\n';
      else if (yytext[2] == '0')
        toStore = '\0';
      else toStore = yytext[2];
    }
    else toStore = yytext[1];
    $$ = expr_create(EXPR_KIND_CHAR, 0, 0, 0, toStore, 0, 0);}
  | TOKEN_INTEGER_LITERAL
  { $$ = expr_create(EXPR_KIND_INT, 0, 0, 0,  atoi(yytext), 0, 0);}
  | TOKEN_TRUE
  { $$ = expr_create(EXPR_KIND_BOOLEAN, 0, 0, 0,  1, 0, 0);}
  | TOKEN_FALSE
  { $$ = expr_create(EXPR_KIND_BOOLEAN, 0, 0, 0,  0, 0, 0);}
  ;

functioncall:  name TOKEN_OPENPAREN arg_list TOKEN_CLOSEPAREN
  {$$ = expr_create(EXPR_KIND_CALL, $1, $3, 0, 0, 0, 0);}
;

name: TOKEN_IDENTIFIER
  {  $$ = expr_create(EXPR_KIND_NAME, 0, 0, strdup(yytext), 0, 0, 0); }
  ;

param: TOKEN_IDENTIFIER
  {  $$ = strdup(yytext);}
  ;
  
param_list: param_list_not_empty 
  {$$ = $1;}
  |
  {$$ = 0;} 
  ;
  
param_list_not_empty: type param TOKEN_COMMA param_list_not_empty
  {$$ = param_list_create($2, $1, $4);}
  | type param
  {$$ = param_list_create($2, $1, 0);}
  ;


%%

int yyerror( char * str)
{
  printf("parse error:%s \nError found on this text:<%s> \n", str, 
yytext);  
}
